<!DOCTYPE html>
<!--
Copyright 2016 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
<link rel="import" href="/tracing/value/histogram.html">

<script>
'use strict';

tr.exportTo('tr.metrics.sh', function() {
  const LONG_TASK_MS = 50;

  // Anything longer than this should be so rare that its length beyond this is
  // uninteresting.
  const LONGEST_TASK_MS = 1000;

  /**
   * This helper function calls |cb| for each of the top-level tasks on the
   * given thread in the given range whose duration is longer than LONG_TASK_MS.
   *
   * @param {tr.model.Thread} thread
   * @param {tr.b.math.Range=} opt_range
   * @param {function()} cb
   * @param {Object=} opt_this
   */
  function iterateLongTopLevelTasksOnThreadInRange(
      thread, opt_range, cb, opt_this) {
    thread.sliceGroup.topLevelSlices.forEach(function(slice) {
      if (opt_range &&
          !opt_range.intersectsExplicitRangeInclusive(slice.start, slice.end)) {
        return;
      }

      if (slice.duration < LONG_TASK_MS) return;

      cb.call(opt_this, slice);
    });
  }

  /**
   * This helper function calls |cb| for each of the main renderer threads in
   * the model.
   *
   * @param {tr.model.Model} model The model.
   * @param {function()} cb Callback.
   * @param {Object=} opt_this Context object.
   */
  function iterateRendererMainThreads(model, cb, opt_this) {
    const modelHelper = model.getOrCreateHelper(
        tr.model.helpers.ChromeModelHelper);
    if (modelHelper !== undefined) {
      Object.values(modelHelper.rendererHelpers).forEach(
          function(rendererHelper) {
            if (!rendererHelper.mainThread) return;

            cb.call(opt_this, rendererHelper.mainThread);
          });
    }
  }

  const BIN_BOUNDARIES = tr.v.HistogramBinBoundaries.createLinear(
      LONG_TASK_MS, LONGEST_TASK_MS, 40);

  /**
   * This metric directly measures long tasks on renderer main threads.
   * This metric requires only the 'toplevel' tracing category.
   *
   * @param {!tr.v.HistogramSet} histograms
   * @param {!tr.model.Model} model
   * @param {!Object=} opt_options
   */
  function longTasksMetric(histograms, model, opt_options) {
    const rangeOfInterest = opt_options ? opt_options.rangeOfInterest :
      undefined;
    const longTaskHist = histograms.createHistogram(
        'longTasks', tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], {
          binBoundaries: BIN_BOUNDARIES,
          description: 'durations of long tasks',
        });

    const relatedNames = new tr.v.d.RelatedNameMap();
    longTaskHist.diagnostics.set('categories', relatedNames);

    iterateRendererMainThreads(model, function(thread) {
      iterateLongTopLevelTasksOnThreadInRange(
          thread, rangeOfInterest, function(task) {
            const breakdown = new tr.v.d.Breakdown();
            breakdown.colorScheme =
              tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;
            for (const slice of task.descendentSlices) {
              const sample = slice.cpuSelfTime;
              if (sample === undefined) continue;

              const category = model.getUserFriendlyCategoryFromEvent(slice);
              const histName = 'longTasks:' + category;
              let hist = histograms.getHistogramNamed(histName);
              if (hist === undefined) {
                hist = histograms.createHistogram(histName,
                    tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, [], {
                      binBoundaries: BIN_BOUNDARIES,
                    });
                relatedNames.set(category, hist.name);
              }
              hist.addSample(sample, {
                events: new tr.v.d.RelatedEventSet([slice]),
              });
              breakdown.set(category, sample + breakdown.get(category));
            }

            longTaskHist.addSample(task.duration, {
              events: new tr.v.d.RelatedEventSet([task]),
              categories: breakdown,
            });
          });
    });
  }

  tr.metrics.MetricRegistry.register(longTasksMetric, {
    supportsRangeOfInterest: true,
    requiredCategories: ['toplevel'],
  });

  return {
    longTasksMetric,
    iterateLongTopLevelTasksOnThreadInRange,
    iterateRendererMainThreads,
    LONG_TASK_MS,
    LONGEST_TASK_MS,
  };
});
</script>
